import type { RawData } from 'ws'
import { dialog, ipcMain } from 'electron'
import { WebSocket } from 'ws'

enum WsReadyState {
  CONNECTING,
  OPEN,
  CLOSING,
  CLOSED
}

enum WsPacketType {
  HAND_SHAKE = 1,
  HAND_SHAKE_ACK,
  MSG,
  HEARTBEAT,
  KICK
}

enum ErrorNo {
  SUCCEED,
  FAILED
}

interface WsReqShakeHandPacketData {
  code: string
}
interface WsReqShakeHandPacket {
  enum: WsPacketType.HAND_SHAKE
  data: WsReqShakeHandPacketData
}

interface WsReqShakeHandAckPacket {
  enum: WsPacketType.HAND_SHAKE_ACK
}

interface WsReqMsgPacketData<T = any> {
  serial_no: string
  id: number
  sub_id: number
  data?: T
}
interface WsReqMsgPacket {
  enum: WsPacketType.MSG
  data: WsReqMsgPacketData
}

interface WsReqHeartbeatPacket {
  enum: WsPacketType.HEARTBEAT
}

type WsReqPacket = WsReqShakeHandPacket | WsReqShakeHandAckPacket | WsReqMsgPacket | WsReqHeartbeatPacket

interface WsRespShakeHandPacketData {
  user_id: number
  name: string
  avatar: string
  role: number
}

interface WsRespMsgPacketData {
  serial_no: string
  id: number
  sub_id: number
  err_no: ErrorNo
  message: string
  data: string
}

interface WsRespPacket {
  enum: WsPacketType
  data: string
}

const WS_HOST = 'ws://192.168.20.188:16305/ws'
let heartBeatTimer: NodeJS.Timeout | undefined = undefined
let websocket: ReturnType<typeof createWebsocket> | null = null

export function createWebsocket() {
  let instance = doCreateWebsocket()
  let kickFlag = false

  function doCreateWebsocket() {
    const ws = new WebSocket(WS_HOST)
    ws.on('error', () => {
      // TODO:
      console.log('xx')
    })
    ws.on('message', (packet: RawData) => {
      const parsedPacket = JSON.parse(packet.toString()) as WsRespPacket
      if (parsedPacket.enum === WsPacketType.KICK) {
        kickFlag = true
        emitLogout(parsedPacket.data)
      }
    })
    ws.on('close', () => {
      if (kickFlag) {
        kickFlag = false
        return
      }
      emitLogout('异常原因导致与服务器断开连接，需重新登录并发起连接')
      // 重新发起连接
      instance = doCreateWebsocket()
    })
    return ws
  }
  function sendHandShake(data: WsReqShakeHandPacketData): Promise<WsRespShakeHandPacketData> {
    return new Promise( (resolve, reject) => {
      send({ enum: WsPacketType.HAND_SHAKE, data })
        .then(() => {
          const listener = (packet: RawData) => {
            const parsedPacket = JSON.parse(packet.toString()) as WsRespPacket
            if (parsedPacket.enum === WsPacketType.HAND_SHAKE) {
              instance.off('message', listener)
              const parsedData = JSON.parse(parsedPacket.data) as WsRespShakeHandPacketData
              if (!parsedData.user_id) {
                emitLogout('用户未激活，联系管理员并重新登录')
                reject()
                return
              }
              sendHandShakeAck()
                .then(() => {
                  initHeartbeatTime()
                  resolve(parsedData)
                })
            }
          }
          instance.on('message', listener)
        })
        .catch(reject)
    })
  }
  async function sendHandShakeAck() {
    await send({ enum: WsPacketType.HAND_SHAKE_ACK })
  }
  function initHeartbeatTime() {
    if (heartBeatTimer) {
      clearInterval(heartBeatTimer)
      heartBeatTimer = undefined
    }
    heartBeatTimer = setInterval(sendHeartBeat, 5000)
  }
  async function sendHeartBeat() {
    await send({ enum: WsPacketType.HEARTBEAT })
  }
  function sendMsg<T, R>(id: number, sub_id: number, data?: T): Promise<R> {
    return new Promise((resolve, reject) => {
      const serial_no = Date.now().toString()
      send({ enum: WsPacketType.MSG, data: { serial_no, id, sub_id, data: data || {} } })
        .then(() => {
          const listener = (packet: RawData) => {
            const parsedPacket = JSON.parse(packet.toString()) as WsRespPacket
            if (parsedPacket.enum === WsPacketType.MSG) {
              const parsedData = JSON.parse(parsedPacket.data) as WsRespMsgPacketData
              if (parsedData.id === id && parsedData.sub_id === sub_id && parsedData.serial_no === serial_no) {
                instance.off('message', listener)
                if (parsedData.err_no === ErrorNo.FAILED) {
                  dialog.showMessageBoxSync({
                    message: parsedData.message,
                    type: 'error'
                  })
                  reject(parsedData.message)
                } else {
                  resolve(parsedData.data ? JSON.parse(parsedData.data) : parsedData.data)
                }
              }
            }
          }
          instance.on('message', listener)
        })
        .catch(reject)
    })
  }
  function receiveMsg(id: number, sub_id: number, callback: (...args: any[]) => void) {
    const listener = (packet: RawData) => {
      const parsedPacket = JSON.parse(packet.toString()) as WsRespPacket
      if (parsedPacket.enum === WsPacketType.MSG) {
        const parsedData = JSON.parse(parsedPacket.data) as WsRespMsgPacketData
        if (parsedData.id === id && parsedData.sub_id === sub_id) {
          instance.off('message', listener)
          callback(parsedData.data ? JSON.parse(parsedData.data) : parsedData.data)
        }
      }
    }
    instance.on('message', listener)
  }
  async function send(packet: WsReqPacket)  {
    if (instance.readyState === WsReadyState.CONNECTING) {
      await open()
    }
    instance.send(stringifyPacket(packet))
  }
  function open() {
    return new Promise(resolve => {
      const listener = () => {
        resolve(true)
        instance.off('open', listener)
      }
      instance.on('open', listener)
    })
  }
  function stringifyPacket(packet: WsReqPacket) {
    let ret = ''
    if (packet.enum === WsPacketType.HAND_SHAKE) {
      ret = JSON.stringify({
        enum: packet.enum,
        data: JSON.stringify(packet.data)
      })
    } else if (packet.enum === WsPacketType.MSG) {
      ret = JSON.stringify({
        enum: packet.enum,
        data: JSON.stringify({
          serial_no: packet.data.serial_no,
          id: packet.data.id,
          sub_id: packet.data.sub_id,
          data: packet.data.data ? JSON.stringify(packet.data.data) : ''
        })
      })
    } else {
      ret = JSON.stringify(packet)
    }
    return ret
  }
  function emitLogout(message: string) {
    dialog.showMessageBoxSync({
      type: 'error',
      message
    })
    ipcMain.emit('did-logout')
  }

  return {
    sendHandShake,
    sendMsg,
    receiveMsg
  }
}

export function useWebsocket() {
  if (!websocket) {
    websocket = createWebsocket()
  }
  return websocket
}
